package com.yanti.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yanti.common.constant.RedisConstants;
import com.yanti.domain.ResponseResult;
import com.yanti.domain.entity.UserDailyStudyTime;
import com.yanti.domain.enums.AppHttpCodeEnum;
import com.yanti.mapper.UserDailyStudyTimeMapper;
import com.yanti.service.UserDailyQuestionCountService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import com.yanti.service.UserDailyStudyTimeService;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;

import static com.yanti.common.constant.RedisConstants.USER_TODAY_STUDY_TIME_PREFIX;

/**
 * (UserDailyStudyTime)表服务实现类
 *
 * @author luo
 * @since 2024-12-16 10:54:35
 */
@Service("userDailyStudyTimeService")
@Slf4j
public class UserDailyStudyTimeServiceImpl extends ServiceImpl<UserDailyStudyTimeMapper, UserDailyStudyTime> implements UserDailyStudyTimeService {

    @Resource
    private UserDailyStudyTimeMapper userDailyStudyTimeMapper;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private UserDailyQuestionCountService userDailyQuestionCountService;
    @Override
    public ResponseResult getTotalStudyTimeInMinutes(String userId) {
        // 获取累计刷题时间（单位：分钟）
        long totalStudyTimeInMillis = getAccumulatedStudyTimeUntilNow(userId);

        return ResponseResult.okResult(totalStudyTimeInMillis*60) ;
    }

    /**
     * 获取截止到当前的累计刷题时间（包含数据库中历史所有记录以及Redis中暂存题目累计用时）
     *
     * @param userId 用户ID
     * @return 累计刷题时间（单位：毫秒）
     */
    private long getAccumulatedStudyTimeUntilNow(String userId) {
        // todo redis的hash结构存储
        // 从数据库获取历史累计时间（单位：毫秒）
        long accumulatedStudyTimeFromDb = getTotalAccumulatedStudyTime(userId);

//        // 使用 SCAN 命令安全遍历 Redis 键（修复键格式问题）
//        String pattern = RedisKeyEnum.TEMPORARY_QUESTION_TIME.getKey(userId, "*");
//        log.info("pattern:{}",pattern);
//        long accumulatedStudyTimeFromRedis = scanAndSumRedisValues(pattern);

        // 从Redis中获取今日累计时间，redis存的是毫秒
        String dateKey = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String todayStudyTimeKey = RedisConstants.buildUserTodayStudyTimeKey(userId, dateKey);
        Object cachedObj = redisTemplate.opsForValue().get(todayStudyTimeKey);
        long cachedStudyTime = 0L;
        if (cachedObj != null) {
            cachedStudyTime = parseRedisValueToLong(cachedObj);
        }else{
            long defaultValue = 0L;
            redisTemplate.opsForValue().set(todayStudyTimeKey, defaultValue,60, TimeUnit.SECONDS);
            log.info("缓存未命中，已缓存默认值：uid={}", userId);
        }
        // 返回总时间（单位：毫秒，移除错误的除法操作）
        return accumulatedStudyTimeFromDb/(1000 * 60) + cachedStudyTime/(1000 * 60);
    }

    private Long getTotalAccumulatedStudyTime(String userId) {
        // 获取该用户所有记录的累计时间，但是需要排除掉当天的记录
        QueryWrapper<UserDailyStudyTime> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", userId)
                .ne("date", LocalDate.now());
        List<UserDailyStudyTime> dailyStudyTimeList = userDailyStudyTimeMapper.selectList(queryWrapper);
        long accumulatedStudyTime = 0;
        for (UserDailyStudyTime record : dailyStudyTimeList) {
            accumulatedStudyTime += record.getTotalStudyTime();
        }
        return accumulatedStudyTime;
    }

//    @Override
//    public ResponseResult getTodayStudyTimeInMinutes(String uid) {
//        // 获取当前日期
//        LocalDate currentDate = LocalDate.now();
//        Date sqlDate = java.sql.Date.valueOf(currentDate);
//
//        QueryWrapper<UserDailyStudyTime> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("uid", uid).eq("date", sqlDate);
//        // 查询该用户当日的刷题时间记录
//        UserDailyStudyTime record = userDailyStudyTimeMapper.selectOne(queryWrapper);
//
//        long totalStudyTimeInSeconds = 0;
//        if (record!= null) {
//            // 如果有记录，获取当日刷题总时长，数据库的是毫秒
//            totalStudyTimeInSeconds = record.getTotalStudyTime();
//        }
//
//        // 将秒转换为分钟，毫秒转为分钟，除以60000
//        long totalStudyTimeInMinutes = totalStudyTimeInSeconds / (60*1000);
//
//        return ResponseResult.okResult(totalStudyTimeInMinutes);
//    }
    /**
     * 获取用户当日刷题时间（返回给前端是分钟为单位），若没有该用户的当日记录则默认返回零
     *
     * @param uid 用户ID
     * @return 包含用户当日刷题时间（秒为单位）的Result对象
     */
    @Override
    public ResponseResult getTodayStudyTimeInMinutes(String uid) {
        // 直接从Redis中获取，若没有则返回零
        String dateKey = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String todayStudyTimeKey = RedisConstants.buildUserTodayStudyTimeKey(uid, dateKey);
        Object cachedObj = null;
        try {
            cachedObj = redisTemplate.opsForValue().get(todayStudyTimeKey);
        } catch (Exception e) {
            log.error("获取redis数据失败，异常如下："+e.getMessage(), e);
        }
        if (cachedObj != null) {
            // 返回秒数，需要除以1000
//            return ResponseResult.okResult(parseRedisValueToLong(cachedObj)/(1000 * 60));
            return ResponseResult.okResult(parseRedisValueToLong(cachedObj)/1000);
        }
        // 缓存没有命中，从数据库中获取
        UserDailyStudyTime record = getTodayStudyTimeFromDB(uid);
        if(record != null){
            Long totalStudyTime = record.getTotalStudyTime();
            // 同步回缓存，写到Redis中
            redisTemplate.opsForValue().set(todayStudyTimeKey, totalStudyTime,20, TimeUnit.MINUTES);
            return ResponseResult.okResult(totalStudyTime/1000);
        }else{
            long defaultValue = 0L;
            redisTemplate.opsForValue().set(todayStudyTimeKey, defaultValue,60, TimeUnit.SECONDS);
            log.debug("缓存未命中，已缓存默认值：uid={}", uid);
            // 直接返回零
            return ResponseResult.okResult(defaultValue);
        }
    }
//    /**
//     * 获取用户当日刷题时间（返回给前端是分钟为单位），若没有该用户的当日记录则默认返回零
//     *
//     * @param uid 用户ID
//     * @return 包含用户当日刷题时间（分钟为单位）的Result对象
//     */
//    @Override
//    public ResponseResult getTodayStudyTimeInMinutes(String uid) {
//        LocalDate currentDate = LocalDate.now();
//        Date sqlDate = java.sql.Date.valueOf(currentDate);
//
//        QueryWrapper<UserDailyStudyTime> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("uid", uid).eq("date", sqlDate);
//        // 查询该用户当日的刷题时间记录
//        UserDailyStudyTime record = userDailyStudyTimeMapper.selectOne(queryWrapper);
//
//        long totalStudyTimeInSeconds = 0;
//        if (record!= null) {
//            // 如果有记录，获取当日刷题总时长，数据库的是毫秒
//            totalStudyTimeInSeconds = record.getTotalStudyTime();
//        }
//
//        // 从 Redis 中获取当天暂存题目累计用时并累加
////        Set<String> todayTemporaryKeys = redisTemplate.keys("temporary_question_time:" + uid + ":*");
////        if (todayTemporaryKeys!= null &&!todayTemporaryKeys.isEmpty()) {
////            for (String key : todayTemporaryKeys) {
////                Object cachedObj = redisTemplate.opsForValue().get(key);
////                Long cachedQuestionTime = 0L;
////                if (cachedObj instanceof Number) {
////                    cachedQuestionTime = ((Number) cachedObj).longValue();
////                }
////                totalStudyTimeInSeconds += cachedQuestionTime / 1000;
////            }
////        }
//        // todo Redis的hash结构存储
//        // 使用 SCAN 命令安全遍历 Redis 键（修复键格式问题）
//        String pattern = RedisKeyEnum.TODAY_TEMPORARY_COUNT.getKey(uid, "*");
//        log.info("pattern:{}",pattern);
//        long accumulatedStudyTimeFromRedis = scanAndSumRedisValues(pattern);
//        log.info("累计的时间：accumulatedStudyTimeFromRedis:{}",accumulatedStudyTimeFromRedis);
//        totalStudyTimeInSeconds += accumulatedStudyTimeFromRedis;
//        // 毫秒转为分钟，除以60000
//        long totalStudyTimeInMinutes = totalStudyTimeInSeconds / (60 * 1000);
//        return ResponseResult.okResult(totalStudyTimeInMinutes);
//    }

    /**
     * 使用 SCAN 命令安全遍历 Redis 键并累加值
     * @param pattern Redis 键的匹配模式 例如："temporary_question_time:user1:*"
     * 注意：pattern 中的 * 表示通配符，用于匹配任意字符
     * @return 累加后的总时间（单位：毫秒）
     */
    public long scanAndSumRedisValues(String pattern) {
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .match(pattern)
                .count(100)
                .build();

        // 使用 Cursor<String> 替代 Cursor<byte[]>
        Cursor<String> cursor = redisTemplate.scan(scanOptions);
        long sum = 0;
        try {
            while (cursor.hasNext()) {
                // 直接获取 String 类型的键，无需转换字节数组
                String key = cursor.next();
                Object value = redisTemplate.opsForValue().get(key);
                sum += parseRedisValueToLong(value);
            }
        } finally {
            cursor.close();
        }
        return sum;
    }

    /**
     * 解析 Redis 中的值为 Long 类型
     * @param value Redis 中的值
     * @return 解析后的 Long 类型值 如果无法解析为 Long，返回 0L 或抛出异常
     */
    public long parseRedisValueToLong(Object value) {
        if (value instanceof Number) {  // 直接处理数值类型
            return ((Number) value).longValue();
        } else if (value instanceof String) {  // 处理字符串序列化的数值
            try {
                return Long.parseLong((String) value);
            } catch (NumberFormatException e) {
                log.error("非数值类型: {}",value);
            }
        }
        return 0L;
    }

    /**
     * 对数据库更新用户当日刷题时间
     * @param uid 用户ID
     * @param studyTimeInMinutes 学习时间
     * @param questionIds
     * @return 是否更新成功
     */
    @Override
    public Boolean updateUserDailyStudyTime4DB(String uid, long studyTimeInMinutes, List<Long> questionIds) {
        // 获取当日日期
        LocalDate currentDate = LocalDate.now();
        Date sqlDate = java.sql.Date.valueOf(currentDate);
        // 如果该用户当日有刷题记录，则更新
        QueryWrapper<UserDailyStudyTime> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", uid).eq("date", sqlDate);
        UserDailyStudyTime record = userDailyStudyTimeMapper.selectOne(queryWrapper);
        // todo 字段不自动更新的问题
        if (record!= null) {
            record.setTodayQuestionCount(record.getTodayQuestionCount() + questionIds.size());
            record.setTotalStudyTime(record.getTotalStudyTime() + studyTimeInMinutes);
            record.setUpdatedAt(new Date());
            return updateById(record);
        }else{
            UserDailyStudyTime newRecord = new UserDailyStudyTime();
            newRecord.setUid(uid);
            newRecord.setDate(sqlDate);
            newRecord.setTodayQuestionCount((long) questionIds.size());
            newRecord.setTotalStudyTime(studyTimeInMinutes);
            newRecord.setUpdatedAt(new Date());
            newRecord.setCreatedAt(new Date());
            boolean save = save(newRecord);
            if(!save){
                log.error("保存用户当日刷题时间失败");
                return false;
            }
        }
        // 删除缓存，避免数据不一致
        String cacheKey = RedisConstants.
                buildUserTodayStudyTimeKey(uid, currentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        try {
            redisTemplate.delete(cacheKey);
            log.info("成功删除Redis缓存数据，key: {}", cacheKey);
        } catch (Exception e) {
            log.error("Redis删除用户当日刷题时间失败", e);
        }
        return true;
    }

    /**
     * 对Redis更新用户当日刷题时间
     * @param uid 用户ID
     * @param studyTimeInMinutes 学习时间
     * @return 是否更新成功
     */
    @Override
    public Boolean updateUserDailyStudyTime4Cache(String uid, long studyTimeInMinutes) {
        // 获取当日日期，并构建缓存的键
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String todayStudyTimeKey = RedisConstants.buildUserTodayStudyTimeKey(uid, date);
        try {
            // 获取当前Redis中的值，并转换为Long类型
            Object currentValueObj = redisTemplate.opsForValue().get(todayStudyTimeKey);
            long currentValue = (currentValueObj instanceof Number) ? ((Number) currentValueObj).longValue() : 0L;
            // 如果该用户当日有刷题记录，则更新
            long newValue = currentValue  + studyTimeInMinutes;
            // 设置Redis中的值
            redisTemplate.opsForValue().set(todayStudyTimeKey, newValue);
            return true;
        } catch (Exception e) {
            log.error("Redis 更新失败：", e);
            return false;
        }
    }
    // todo 删除用户以往的刷题时间，使用Redis管道进行优化
    @Override
    public Boolean deleteUserDailyStudyTime4Cache(String uid) {
        // 获取当前日期，用于排除今天的记录
        String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

        // 获取Redis连接
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            try {
                // 构建用户历史学习时间的键模式（排除今天）
                String keyPattern = USER_TODAY_STUDY_TIME_PREFIX + uid + ":*";
                // 使用SCAN命令替代KEYS，避免阻塞Redis
                ScanOptions options = ScanOptions.scanOptions()
                        .match(keyPattern)
                        .count(100)
                        .build();

                // 创建管道
                connection.openPipeline();
                log.info("keyPattern={}",keyPattern);
              // 遍历所有匹配的键并添加到删除队列，并删除过期的键，cursor与Pipeline可能会有冲突
                int deletedCount = 0;
                Cursor<byte[]> cursor = null;
                try {
                    connection.closePipeline(); // 先关闭 pipeline 避免冲突
                    cursor = connection.scan(options);

                    connection.openPipeline(); // 重新打开 pipeline 执行删除

                    while (cursor.hasNext()) {
                        byte[] key = cursor.next();
                        String keyStr = new String(key);
                        String datePart = keyStr.substring(keyStr.lastIndexOf(":") + 1);
                        if (!datePart.equals(today)) {
                            connection.del(key);
                            deletedCount++;
                        }
                    }
                } finally {
                    if (cursor != null) cursor.close();
                }
                log.info("删除了 {} 个键", deletedCount);
                // 执行管道命令
                List<Object> results = connection.closePipeline();

                // 检查是否所有删除操作都成功
                boolean allSuccess = true;
                for (Object result : results) {
                    if (result instanceof Long && (Long) result == 0) {
                        allSuccess = false;
                        break;
                    }
                }
                return allSuccess;
            } catch (Exception e) {
                log.error("Redis管道删除失败：", e);
                return false;
            }
        });
    }

    @Override
    public Boolean updateUserQuestionDailyCount4Cache(String uid, List<Long> questionIds) {
        // 采用了Hash存储结构去统计用户刷题数量，示例键：question_count:1001:20231001，内部字段为 {questionId: count}
        try {
            // 获取当前日期
            String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            // 构建键
            String questionCountKey = RedisConstants.buildUserQuestionCountKey(uid, date);
            // 增加缓存中存储的questionId的count值，直接采用increment命令，即使不存在的键，也会自动创建
            for (Long questionId : questionIds) {
                redisTemplate.opsForHash().increment(questionCountKey, questionId.toString(), 1);
            }
        } catch (Exception e) {
            log.error("Redis 更新失败：", e);
            return false;
        }
        return true;
    }

    // todo 获取用户刷题总数量，使用Scan命令进行优化
    @Override
    public Integer getUserQuestionCountInCache(String uid) {
        // 获取当前日期
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        // 构建键
        String questionCountKey = RedisConstants.buildUserQuestionCountKey(uid, date);

        // 修改ScanOptions的配置，移除match参数，直接扫描整个Hash结构
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .count(100)
                .build();

        // 在 try-with-resources 块结束时，cursor.close() 会被自动调用，确保底层资源被释放。
        try (Cursor<Map.Entry<Object, Object>> cursor = redisTemplate
                .opsForHash()
                .scan(questionCountKey, scanOptions)) {
            int sum = 0;
            while (cursor.hasNext()) {
                Map.Entry<Object, Object> entry = cursor.next();
                sum += parseRedisValueToLong(entry.getValue());
            }
            return sum;
        } catch (Exception e) {
            log.error("Redis 获取失败：", e);
            return null;
        }
    }

    @Override
    public ResponseResult getUserQuestionCount(String uid) {
        // 参照获取用户当日学习时间，先从Redis中获取，如果获取失败，则从数据库中获取，然后写入Redis中
        // 先从redis获取
        // 获取当前日期的字符串形式
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String countKey = RedisConstants.buildUserQuestionCountKey(uid, date);
        // 判断redis中的数据是否存在
        if (Boolean.TRUE.equals(redisTemplate.hasKey(countKey))) {
            Long size = redisTemplate.opsForHash().size(countKey);
            if (size > 0) {
                // 获取Redis中的数据，遍历hash键值对，并累加
                Integer sum = redisTemplate.opsForHash().values(countKey)
                        .stream()
                        .mapToInt(value -> (int) parseRedisValueToLong(value))
                        .sum();
                return ResponseResult.okResult(sum);
            }
        }
        // 没有缓存数据，从数据库中获取
        try {
            HashMap<Long, Integer> questionCounts = userDailyQuestionCountService.getUserTodayQuestionCount(uid);
            if (questionCounts != null) {
                if (questionCounts.size() > 0) {
                    Map<String, Integer> stringQuestionCounts = questionCounts.entrySet()
                            .stream()
                            .collect(Collectors.toMap(
                                    e -> e.getKey().toString(),
                                    Map.Entry::getValue
                            ));
                    redisTemplate.opsForHash().putAll(countKey, stringQuestionCounts);
                    // 设置过期时间，20分钟
                    redisTemplate.expire(countKey, RedisConstants.CODE_EXPIRE_TIME, TimeUnit.MINUTES);
                    return ResponseResult.okResult(
                            questionCounts.values()
                                    .stream()
                                    .mapToInt(Integer::intValue)
                                    .sum());
                }
            }
            return ResponseResult.okResult(0);
        }
        catch (Exception e) {
            log.error("数据库获取失败：", e);
            return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
        }
    }
    // Hash 结构中的 field 数量不是特别大的时候用下面的代码
//    @Override
//    public Integer getUserQuestionCountInCache(String uid) {
//        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
//        String questionCountKey = RedisConstants.buildUserQuestionCountKey(uid, date);
//
//        try {
//            Map<Object, Object> entries = redisTemplate.opsForHash().entries(questionCountKey);
//            int sum = 0;
//            for (Object value : entries.values()) {
//                sum += parseRedisValueToLong(value);
//            }
//            return sum;
//        } catch (Exception e) {
//            log.error("Redis 获取失败：", e);
//            return null;
//        }
//    }
    /**
     * 从数据库查询当日学习时间
     */
    private UserDailyStudyTime getTodayStudyTimeFromDB(String uid) {
        LocalDate currentDate = LocalDate.now();
        QueryWrapper<UserDailyStudyTime> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", uid)
                .eq("date", java.sql.Date.valueOf(currentDate));
        return userDailyStudyTimeMapper.selectOne(queryWrapper);
    }
}